package com.google.enterprise.mailmigration;

import com.google.gdata.client.appsforyourdomain.migration.MailItemService;
import com.google.gdata.data.appsforyourdomain.migration.Label;
import com.google.gdata.data.appsforyourdomain.migration.MailItemEntry;
import com.google.gdata.data.appsforyourdomain.migration.MailItemFeed;
import com.google.gdata.data.appsforyourdomain.migration.Rfc822Msg;
import com.google.gdata.data.batch.BatchStatus;
import com.google.gdata.data.batch.BatchUtils;
import com.google.gdata.util.AuthenticationException;
import com.google.gdata.util.ServiceException;

import java.io.ByteArrayOutputStream;
import java.io.IOException;

import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.Random;
import java.util.logging.Logger;

import javax.mail.Folder;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.NoSuchProviderException;
import javax.mail.Session;

import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.configuration.XMLConfiguration;

public class UserMigrator {
    //Logger class
    private static final Logger logger =
        Logger.getLogger(UserMigrator.class.getName());

    //Configuration class
    XMLConfiguration config = null;

    //User information
    private String oldUser;
    private String oldPassword;
    private String gooDomain;
    private String gooUser;
    private MailItemService gooMailItemService;

    //migration values
    private String gooAdminUser = null;
    private String gooAdminPassword = null;
    private static String APPS_FEEDS_URL_BASE = null;
    private static String SERVICE_VERSION = "2.0";
    protected final String domainUrlBase;

    //IMAP values
    private static Session session = null;
    private javax.mail.Store store = null;

    MigrationConfig migConf = new MigrationConfig();

    public UserMigrator(String ou, String op, String gd,
                        String gu) throws ConfigurationException {
        this.setOldUser(ou);
        this.setOldPassword(op);
        this.setGooDomain(gd);
        domainUrlBase = getAPPS_FEEDS_URL_BASE() + gd + "/";
        this.setGooUser(gu);
    }

    public void gooConnect() throws AuthenticationException {
        // Set up the mail item service.
        gooMailItemService = new MailItemService("imap-Migration");
        logger.info("Connecting to Google with admin user " +
                    migConf.getGooAdminUser() + "...");
        gooMailItemService.setUserCredentials(migConf.getGooAdminUser(),
                                              migConf.getGooAdminPassword());
        logger.info("Connection to Google with admin user " + gooAdminUser +
                    " done.");
    }

    public void oldConnect() throws NoSuchProviderException,
                                    MessagingException {
        Properties props = new Properties();

        if (session == null)
            session = Session.getDefaultInstance(props, null);
        //session.setDebug(true);
        store = session.getStore("imap");

        logger.info("Connection to legacy IMAP server " +
                    migConf.getOldIMAPServer() + " with user " + oldUser +
                    " and password " + oldPassword + "...");
        store.connect(migConf.getOldIMAPServer(), oldUser, oldPassword);
        logger.info("Connection to legacy IMAP server with user " + oldUser +
                    " done.");
    }

    public void oldDisconnect() throws MessagingException {
        if (store != null)
            store.close();
        logger.info("Connection to legacy IMAP server with user " + oldUser +
                    " closed correctly.");
    }


    private MailItemEntry setupMailItem(String rfcText, List<String> labels) {

        // Unique subject and message id are required so that GMail does not
        // suppress duplicate messages
        String randomFactor =
            Integer.toString(100000 + (new Random()).nextInt(900000));
        rfcText =
                rfcText.replace("Subject: Subject", "Subject: Unique Subject " +
                                randomFactor);
        rfcText =
                rfcText.replace("Message-ID: <", "Message-ID: <" + randomFactor);
        Rfc822Msg rfcMsg = new Rfc822Msg(rfcText);

        // create MailItemEntry with appropriate data
        MailItemEntry mailItem = new MailItemEntry();
        mailItem.setRfc822Msg(rfcMsg);
        for (String label: labels) {
            mailItem.addLabel(new Label(label));
        }

        return mailItem;
    }


    private MailItemFeed batchInsertMailItems(MailItemEntry... mailItems) throws ServiceException,
                                                                                 IOException {
        logger.info("Batch insertion: " + Integer.toString(mailItems.length) +
                    " mailItems");

        MailItemFeed feed = new MailItemFeed();
        for (int i = 0; i < mailItems.length; i++) {
            if (mailItems[i] != null) {
                BatchUtils.setBatchId(mailItems[i], Integer.toString(i));
                feed.getEntries().add(mailItems[i]);
            }
        }

        return gooMailItemService.batch(getGooDomain(), getGooUser(), feed);
    }

    //

    private void sendMessages(MailItemEntry[] entries) {
        logger.info("Sending messages to Google...");
        try {
            MailItemFeed feed = batchInsertMailItems(entries);
            // Check for failure in the returned entries.
            int failedInsertions = 0, successfulInsertions = 0, notTriedInsertions = 0;
            for (MailItemEntry returnedEntry: feed.getEntries()) {
                if (BatchUtils.isFailure(returnedEntry)) {
                    BatchStatus status =
                        BatchUtils.getBatchStatus(returnedEntry);
                    logger.severe("Entry " +
                                  BatchUtils.getBatchId(returnedEntry) +
                                  " failed insertion: " + status.getCode() +
                                  " " + status.getReason());
                    failedInsertions++;
                } else {
                    if(returnedEntry == null){
                        notTriedInsertions++;
                    }
                    else{
                        successfulInsertions++;
                    }
                }
            }

            logger.info("Batch insertion: " +
                        Integer.toString(successfulInsertions) +
                        " succeeded, " + Integer.toString(failedInsertions) +
                        " failed, " + Integer.toString(notTriedInsertions) + " not tried." );
        } catch (IOException e) {
            logger.severe("Caught IOException: " + e.toString());
            e.printStackTrace();
        } catch (ServiceException e) {
            logger.severe("Caught ServiceException: " + e.toString());
            e.printStackTrace();
        }
    }
    //

    public void processMessages(Folder folder) throws MessagingException,
                                                      IOException {

        String rfcText = null;

        ByteArrayOutputStream myStream = new ByteArrayOutputStream();

        Message message[] = folder.getMessages();

        List<String> labels = new ArrayList<String>();
        labels.add(folder.getName());

        int numOfMessages = folder.getMessageCount();
        MailItemEntry[] entries = new MailItemEntry[numOfMessages];

        int messageCount = 0; //Pointer to the next message to be processed
        int requestPosition =
            0; //Pointer to the next position in which the message must be copied
        int requestSize = 0;
        do {
            logger.info("Migrating message " + (messageCount + 1) + " of " +
                        numOfMessages);
            if (message[messageCount].getSize() > 20971520) {
                //Ignore the message
                logger.info("The following message bigger than 20Mb found. It will not be migrated");
                logger.info(message[messageCount].getDescription());
                messageCount++;
            } else {
                if ((requestSize + message[messageCount].getSize()) > 33554432) {
                    //Size is bigger than 32Mb. Send now
                    logger.info("Sending messages processed so far to avoid a request bigger than 32 Mb");
                    this.sendMessages(entries);
                    requestSize = 0;
                    entries = new MailItemEntry[numOfMessages - messageCount];
                    requestPosition = 0;
                } else {
                    myStream.reset();
                    message[messageCount].writeTo(myStream);
                    rfcText = myStream.toString();
                    entries[requestPosition] = setupMailItem(rfcText, labels);
                    requestSize += message[messageCount].getSize();
                    messageCount++;
                    requestPosition++;
                }
            }
        } while (messageCount < numOfMessages);

        this.sendMessages(entries);
    }

    public void processFolder(Folder folder) throws MessagingException,
                                                    IOException {
        if (folder.getName() != "") {
            if (folder.getMessageCount() > 0) {
                logger.info("Processing folder: " + folder.getName());
                logger.info("Total number of messages: " +
                            folder.getMessageCount());
                logger.info("Number of new messages: " +
                            folder.getNewMessageCount());
                logger.info("Number of unread messages: " +
                            folder.getUnreadMessageCount());
                folder.open(Folder.READ_ONLY);
                processMessages(folder);
            }
        }
        if ((folder.getType() & Folder.HOLDS_FOLDERS) != 0) {
            Folder[] f = folder.list();
            for (int i = 0; i < f.length; i++)
                processFolder(f[i]);
        }
    }

    public void migrate() throws MessagingException, IOException {
        logger.info("Processing user " + oldUser);
        Folder rf = null;
        //Mientras haya carpetas
        rf = store.getDefaultFolder();
        processFolder(rf);
    }

    public String getOldUser() {
        return oldUser;
    }

    public void setOldUser(String oldUser) {
        this.oldUser = oldUser;
    }

    public String getOldPassword() {
        return oldPassword;
    }

    public void setOldPassword(String oldPassword) {
        this.oldPassword = oldPassword;
    }

    public String getGooDomain() {
        return gooDomain;
    }

    public void setGooDomain(String gooDomain) {
        this.gooDomain = gooDomain;
    }

    public String getGooUser() {
        return gooUser;
    }

    public void setGooUser(String gooUser) {
        this.gooUser = gooUser;
    }

    private void setGooAdminUser(String gooAdminUser) {
        this.gooAdminUser = gooAdminUser;
    }

    private void setGooAdminPassword(String gooAdminPassword) {
        this.gooAdminPassword = gooAdminPassword;
    }

    private void setAPPS_FEEDS_URL_BASE(String APPS_FEEDS_URL_BASE) {
        this.APPS_FEEDS_URL_BASE = APPS_FEEDS_URL_BASE;
    }

    public static String getAPPS_FEEDS_URL_BASE() {
        return APPS_FEEDS_URL_BASE;
    }

    public static String getSERVICE_VERSION() {
        return SERVICE_VERSION;
    }

    private void setSERVICE_VERSION(String SERVICE_VERSION) {
        this.SERVICE_VERSION = SERVICE_VERSION;
    }

}
